﻿/*
	Copyright (C) 2012 Bernardo van der Wal <bernardo@thebigb.nl>

	Permission is hereby granted, free of charge, to any person obtaining a copy of
	this software and associated documentation files (the "Software"), to deal in
	the Software without restriction, including without limitation the rights to
	use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
	of the Software, and to permit persons to whom the Software is furnished to do
	so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/
using System;
using System.Collections.Generic;

namespace TheBigB.JsonSerializerLite
{
	/// 
	/// Thread-safe Dictionary wrapper specialized to perform caching duties.
	/// 
	/// Changelog:
	///		2012-04-30
	///			- Added comments and regions
	///			- Made the maximum cache size a public property
	///			- Prevention of negative maximum size
	///			
	///		2012-09-08
	///			- Added more locking as reading doesn't seem to be safe either
	///			
	/// TODOS:
	///		- Remove least-accessed items first when full
	///		  (implementing an access-counter might be a performance hit though)
	///			

	/// <summary>
	/// Thread-safe Dictionary wrapper specialized to perform caching duties.
	/// </summary>
	/// <typeparam name="TKey">Key type</typeparam>
	/// <typeparam name="TValue">Value type</typeparam>
	public class Cache<TKey, TValue>
	{
		#region ... Non-public fields ...

		/// <summary>
		/// Maximum cache size
		/// </summary>
		private int _maxSize;

		/// <summary>
		/// Cache storage
		/// </summary>
		private Dictionary<TKey, TValue> _data;

		/// <summary>
		/// Lock object for synchronization
		/// </summary>
		private object _mutex;

		#endregion

		#region ... Public fields ...

		/// <summary>
		/// Gets or sets the maximum size for the cache. Size '0' means
		/// unlimited size. If the new size is smaller than the number of 
		/// contents, the cache is emptied.
		/// </summary>
		public int MaxSize { 
			get 
			{
				return this._maxSize;
			} 
			set
			{
				// Prevent negative size
				if (value < 0)
					this._maxSize = 0;
				else
					this._maxSize = value;

				// If the new size is smaller than the count of items, empty the cache
				if (this._maxSize != 0 && this._data.Count > this._maxSize)
					this._data = new Dictionary<TKey, TValue>(this._maxSize);
			}
		}

		#endregion

		#region ... Constructors ...

		/// <summary>
		/// Creates an instance of the Cache class. If no parameter is
		/// provided or '0' is passed as maximum size, an unlimited 
		/// sized cache is configured.
		/// </summary>
		public Cache() : this(0){}

		/// <summary>
		/// Creates an instance of the Cache class. If no parameter is
		/// provided or '0' is passed as maximum size, an unlimited 
		/// sized cache is configured.
		/// </summary>
		/// <param name="maxSize">Maximum size of cache</param>
		public Cache(int maxSize)
		{
			// Prevent negative sizing
			if (maxSize < 0)
				maxSize = 0;

			// Store maximum size and initialize cache storage
			this._maxSize = maxSize;
			this._data = new Dictionary<TKey, TValue>(maxSize);

			// Create lock reference
			this._mutex = new object();
		}

		#endregion

		#region ... Public methods ...

		/// <summary>
		/// Returns the cached value for the given key
		/// </summary>
		/// <param name="key">Key of cache value</param>
		/// <returns>Cached value</returns>
		public TValue this[TKey key]
		{
			get 
			{ 
				lock (_mutex) 
					return this._data[key];
			}
		}

		/// <summary>
		/// Adds a cached value for the provided key
		/// </summary>
		/// <param name="key">Key of value</param>
		/// <param name="value">Value to cache</param>
		public void Add(TKey key, TValue value)
		{
			lock (_mutex)
			{
				// Remove the value if there already is one
				if (this._data.ContainsKey(key))
					this._data.Remove(key);

				// If the maximum cache size is exceeded, remove the first one to make
				// place for the new one
				if (this._maxSize != 0 && this._data.Count == this._maxSize)
				{
					IEnumerator<TKey> enumerator = this._data.Keys.GetEnumerator();
					if (enumerator.MoveNext())
						this._data.Remove(enumerator.Current);
				}

				// Add to cache
				this._data.Add(key, value);
			}
		}

		/// <summary>
		/// Returns whether the provided key has a cached value available.
		/// </summary>
		/// <param name="key">Key to look up</param>
		/// <returns>Boolean value indicating whether a cached value was found</returns>
		public bool Contains(TKey key)
		{
			lock (_mutex) 
				return this._data.ContainsKey(key);
		}

		/// <summary>
		/// Returns whether the provided key has a cached value available and returns
		/// that value in the value argument as an 'out'-parameter.
		/// </summary>
		/// <param name="key">Key to look up</param>
		/// <param name="value">If the value found it is returned with this parameter</param>
		/// <returns>Boolean value indicating whether a cached value was found</returns>
		public bool TryGet(TKey key, out TValue value)
		{
			lock (_mutex)
			{
				if (this._data.ContainsKey(key))
				{
					value = this._data[key];
					return true;
				}
				value = default(TValue);
				return false;
			}
		}

		#endregion
	}
}
